#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <http.h>
#include <dataset.h>

void discard_uri(URI * pURI) {
	if (pURI == NULL)
		return;
	if (pURI->params != NULL) {
		list * pEntries = all_entries(pURI->params);
		map_entry * pEntry = NULL;
		while ((pEntry = (map_entry *) next(pEntries)) != NULL)
			awl_free(pEntry->pData);
		destroy_list(pEntries);
		destroy_map(pURI->params);
		pURI->params = NULL;
	}
}

int parse_uri(URI * pURI, const char * uri, unsigned int pos) {
	if (uri == NULL || pURI == NULL)
		return 1;
	memset(pURI, 0, sizeof(URI));
	pURI->params = create_map(0);
	unsigned int i = 0;
	char tmp[MAX_TMP_BUFFER_LEN];
	// parse path
	while (uri[pos] != '?' && uri[pos] != '#' && uri[pos] != '\0') {
		if (i >= MAX_PATH_LEN - 1) {
			discard_uri(pURI);
			return 2;
		}
		tmp[i++] = uri[pos++];
	}
	tmp[i] = '\0';
	strncpy(pURI->path, tmp, MAX_PATH_LEN - 1);
	i = 0;
	// parse parameters
	if (uri[pos] == '?') {
		pos++;
		char key[MAX_PARAM_KEY_LEN];
		while (uri[pos] != '#' && uri[pos] != '\0') {
			// parse key
			while (uri[pos] != '=' && uri[pos] != '&' && uri[pos] != '#'
					&& uri[pos] != '\0') {
				if (i >= MAX_PARAM_KEY_LEN - 1) {
					discard_uri(pURI);
					return 3;
				}
				tmp[i++] = uri[pos++];
			}
			if (i == 0) {
				if (uri[pos] == '=') {
					discard_uri(pURI);
					return 4;
				} else if (uri[pos] == '&') {
					pos++;
				}
				continue;
			}
			tmp[i] = '\0';
			strncpy(key, tmp, MAX_PARAM_KEY_LEN - 1);
			i = 0;
			if (uri[pos] != '=') {
				void *pOld = put(pURI->params, key, NULL);
				if (pOld != NULL)
					awl_free(pOld);
				if (uri[pos] == '&')
					pos++;
				continue;
			}
			pos++;
			// parse value
			while (uri[pos] != '&' && uri[pos] != '#' && uri[pos] != '\0') {
				if (i >= MAX_PARAM_VALUE_LEN - 1) {
					discard_uri(pURI);
					return 5;
				}
				if (uri[pos] == '%') {
					tmp[i] = 0;
					char ch = uri[++pos];
					if (ch >= '0' && ch <= '9') {
						tmp[i] |= (ch - '0') << 4;
					} else if (ch >= 'A' && ch <= 'F') {
						tmp[i] |= (ch - 'A' + 10) << 4;
					} else if (ch >= 'a' && ch <= 'f') {
						tmp[i] |= (ch - 'a' + 10) << 4;
					} else {
						discard_uri(pURI);
						return 6;
					}
					ch = uri[++pos];
					if (ch >= '0' && ch <= '9') {
						tmp[i] |= (ch - '0');
					} else if (ch >= 'A' && ch <= 'F') {
						tmp[i] |= (ch - 'A' + 10);
					} else if (ch >= 'a' && ch <= 'f') {
						tmp[i] |= (ch - 'a' + 10);
					} else {
						discard_uri(pURI);
						return 7;
					}
				} else if (uri[pos] == '+') {
					tmp[i] = ' ';
				} else {
					tmp[i] = uri[pos];
				}
				i++;
				pos++;
			}
			tmp[i] = '\0';
			char * value = (char *) awl_alloc(sizeof(char) * strlen(tmp) + 1);
			strcpy(value, tmp);
			void *pOld = put(pURI->params, key, value);
			if (pOld != NULL)
				awl_free(pOld);
			i = 0;
			if (uri[pos] == '&')
				pos++;
			else
				break;
		}
	}
	// parse anchor
	if (uri[pos] == '#') {
		while (uri[++pos] != '\0') {
			if (i >= MAX_ANCHOR_LEN - 1) {
				discard_uri(pURI);
				return 8;
			}
			tmp[i++] = uri[pos];
		}
		tmp[i] = '\0';
		strncpy(pURI->anchor, tmp, MAX_ANCHOR_LEN - 1);
	}
	return 0;
}

inline char * str_param(map *params, const char *key, const char *def_value) {
	char *tmp = (char *) get(params, key);
	return tmp == NULL ? (char *) def_value : tmp;
}

inline int int_param(map *params, const char *key, int def_value) {
	char *tmp = (char *) get(params, key);
	return tmp == NULL ? def_value : atoi(tmp);
}

inline float float_param(map *params, const char *key, float def_value) {
	return (float) double_param(params, key, def_value);
}

inline double double_param(map *params, const char *key, double def_value) {
	char *tmp = (char *) get(params, key);
	return tmp == NULL ? def_value : atof(tmp);
}
//-------------------------------------------------------------------------

void discard_http_log_entry(HttpLogEntry * pEntry) {
	if (pEntry == NULL)
		return;
	discard_uri(&pEntry->uri);
}

static inline int _parse_int(const char *line, int pos, int *value) {
	char tmp[MAX_INT_LEN];
	int i = 0;

	while (line[pos] == ' ')
		pos++;

	while (line[pos] >= '0' && line[pos] <= '9' && i < MAX_INT_LEN - 1)
		tmp[i++] = line[pos++];

	tmp[i] = 0;
	*value = atoi(tmp);

	return pos;
}

static inline int _parse_unsigned_short(const char *line, int pos,
		unsigned short *value) {
	int tmp;
	pos = _parse_int(line, pos, &tmp);
	if (pos < 0)
		return -1;
	if (tmp < 0)
		return -2;
	*value = (unsigned short) tmp;
	return pos;
}

static inline int _parse_unsigned_int(const char *line, int pos,
		unsigned int *value) {
	int tmp;
	pos = _parse_int(line, pos, &tmp);
	if (pos < 0)
		return -1;
	if (tmp < 0)
		return -2;
	*value = (unsigned int) tmp;
	return pos;
}

static inline int _parse_token(const char *line, int pos, char *buf,
		const int size) {
	int i = 0;

	while (line[pos] == ' ' || line[pos] == '"')
		pos++;

	while (line[pos] != ' ' && line[pos] != '"' && line[pos] != 0
			&& i < size - 1)
		buf[i++] = line[pos++];

	buf[i] = 0;

	return pos;
}

static inline int _parse_str(const char *line, int pos, char *buf,
		const int size, const char end) {
	int i = 0;

	while (line[pos] != end && line[pos] != 0 && i < size - 1)
		buf[i++] = line[pos++];

	buf[i] = 0;

	if (line[pos] == 0)
		return -1;

	return ++pos;
}

static inline int parse_host(const char *line, int pos, IP *ip) {
	int part;

	if ((pos = _parse_int(line, pos, &part)) < 0 || part < 0 || part > 255)
		return -1;
	ip->parts.a = (unsigned char) part;

	if (line[pos] == '.')
		pos++;

	if ((pos = _parse_int(line, pos, &part)) < 0 || part < 0 || part > 255)
		return -2;
	ip->parts.b = (unsigned char) part;

	if (line[pos] == '.')
		pos++;

	if ((pos = _parse_int(line, pos, &part)) < 0 || part < 0 || part > 255)
		return -3;
	ip->parts.c = (unsigned char) part;

	if (line[pos] == '.')
		pos++;

	if ((pos = _parse_int(line, pos, &part)) < 0 || part < 0 || part > 255)
		return -4;
	ip->parts.d = (unsigned char) part;

	return pos;
}

static inline int parse_identity(const char *line, int pos, char *identity) {
	return _parse_token(line, pos, identity, IDENTITY_LEN);
}

static inline int parse_userid(const char *line, int pos, char *userid) {
	return _parse_token(line, pos, userid, USERID_LEN);
}

static inline int parse_time(const char *line, int pos, Time *time) {
	char tmp[8];
	unsigned int digi, i = 0;

	while (line[pos] == ' ')
		pos++;

	// day
	if (line[pos++] != '[') {
		return -1;
	}

	pos = _parse_unsigned_int(line, pos, &digi);
	if (pos < 0)
		return -2;
	time->date = digi;

	if (line[pos++] != '/')
		return -3;
	while (line[pos] != '/' && i < 4) {
		if (line[pos] == 0)
			return -4;
		tmp[i++] = line[pos++];
	}
	tmp[i] = '\0';
	time->month = GET_MONTH(tmp);

	if (line[pos++] != '/')
		return -5;
	// year
	pos = _parse_unsigned_int(line, pos, &digi);
	if (pos < 0)
		return -6;
	time->year = digi;

	if (line[pos++] != ':')
		return -7;
	// hour
	pos = _parse_unsigned_int(line, pos, &digi);
	if (pos < 0)
		return -8;
	time->hour = digi;

	if (line[pos++] != ':')
		return -9;
	// minute
	pos = _parse_unsigned_int(line, pos, &digi);
	if (pos < 0)
		return -10;
	time->minute = digi;

	if (line[pos++] != ':')
		return -11;
	// second
	pos = _parse_unsigned_int(line, pos, &digi);
	if (pos < 0)
		return -12;
	time->second = digi;

	if (line[pos++] != ' ')
		return -13;
	// timezone
	if (line[pos] == '-')
		time->tz_flag = TZ_FLAG_WEST;
	else if (line[pos] == '+')
		time->tz_flag = TZ_FLAG_EAST;
	else
		return -14;
	pos = _parse_unsigned_int(line, ++pos, &digi);
	if (pos < 0)
		return -15;
	time->tz_hour = digi / 100;
	time->tz_minute = digi % 100;

	if (line[pos] != ']')
		return -16;

	return ++pos;
}

static inline int parse_request(const char *line, int pos, char *method,
		char *url, URI *uri, char *protocol) {
	while (line[pos] == ' ')
		pos++;
	if (line[pos++] != '"')
		return -1;
	pos = _parse_token(line, pos, method, METHOD_LEN);
	if (pos < 0)
		return -2;

	pos = _parse_token(line, pos, url, URL_LEN);
	if (pos < 0)
		return -3;
	parse_uri(uri, url, 0);

	pos = _parse_token(line, pos, protocol, PROTOCOL_LEN);
	if (pos < 0)
		return -4;

	if (line[pos++] != '"')
		return -5;

	return pos;
}

static inline int parse_status(const char *line, int pos,
		unsigned short *status) {
	return _parse_unsigned_short(line, pos, status);
}

static inline int parse_bytes(const char *line, int pos, unsigned int *bytes) {
	// this field may be empty("-")
	char tmp[MAX_INT_LEN];
	int i;

	pos = _parse_token(line, pos, tmp, MAX_INT_LEN);
	if (pos < 0)
		return pos;

	if (tmp[0] == '-') {
		*bytes = 0;
	} else {
		i = atoi(tmp);
		if (i < 0)
			return -1;
		*bytes = (unsigned int) i;
	}

	return pos;
}

static inline int parse_referer(const char *line, int pos, char *referer) {
	return _parse_str(line, pos, referer, REFERER_LEN, '"');
}

static inline int parse_user_agent(const char *line, int pos, char *user_agent) {
	return _parse_str(line, pos, user_agent, USER_AGENT_LEN, '"');
}

/**
 * Long but fast code ^_^
 */
int parse_http_log_entry(HttpLogEntry * pEntry, const char* line) {
	int pos = 0;

	if (line == NULL || pEntry == NULL)
		return 1;
	memset(pEntry, 0, sizeof(HttpLogEntry));

	pos = parse_host(line, pos, &pEntry->ip);
	if (pos < 0)
		return 1;

	pos = parse_identity(line, pos, pEntry->identity);
	if (pos < 0)
		return 2;

	pos = parse_userid(line, pos, pEntry->userid);
	if (pos < 0)
		return 3;

	pos = parse_time(line, pos, &pEntry->time);
	if (pos < 0)
		return 4;

	pos = parse_request(line, pos, pEntry->method, pEntry->url, &pEntry->uri,
			pEntry->protocol);
	if (pos < 0)
		return 5;

	pos = parse_status(line, pos, &pEntry->status);
	if (pos < 0)
		return 6;

	pos = parse_bytes(line, pos, &pEntry->bytes);
	if (pos < 0)
		return 7;

	while (line[pos] != 0 && line[pos] != '"')
		pos++;
	if (line[pos] == 0)
		return 0;

	pos = parse_referer(line, ++pos, pEntry->referer);
	if (pos < 0)
		return 8;

	pos++;
	while (line[pos] != 0 && line[pos] != '"')
		pos++;
	if (line[pos] == 0)
		return 0;

	pos = parse_user_agent(line, ++pos, pEntry->user_agent);
	if (pos < 0)
		return 9;

	return 0;
}

